/*
 * Copyright (c) 2020 - present, Inspur Genersoft Co., Ltd.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *       http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 */

package com.inspur.edp.lcm.metadata.common.util;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.nio.channels.Channels;
import java.nio.channels.FileChannel;
import java.nio.channels.FileLock;
import java.nio.channels.OverlappingFileLockException;
import java.util.Date;
import java.util.Properties;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.SynchronousQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import lombok.extern.slf4j.Slf4j;

/**
 * Properties文件操作工具类
 *
 * @author sunhongfei01
 * @since 2022-4-19
 */
@Slf4j
public class PropertiesUtil {
    private static final int READ_RETRY_TIMES = 5;
    /**
     * 写入Properties文件线程池
     */
    private static final ExecutorService executor = new ThreadPoolExecutor(0, 20,
            60L, TimeUnit.SECONDS, new SynchronousQueue<>());

    /**
     * 读取Properties文件
     *
     * @param filePath Properties文件路径
     * @return Properties文件内容
     */
    public static Properties getProperties(String filePath) {
        File file = new File(filePath);
        Properties properties = new Properties();
        try (FileInputStream inputStream = new FileInputStream(file)) {
            properties = loadPropertiesMultiTimes(inputStream);
        } catch (FileNotFoundException e) {
            log.warn("业务对象中元数据信息缓存文件[{}]不存在!", filePath);
        } catch (Exception e) {
            log.error("获取业务对象中元数据信息缓存文件[{}]失败!", filePath, e);
        }
        return properties;
    }

    private static Properties loadPropertiesMultiTimes(FileInputStream inputStream) throws Exception {
        Properties properties = new Properties();
        Exception exception = null;
        int times = 0;
        boolean hasReadProps = false;
        while (!hasReadProps && ++times < READ_RETRY_TIMES) {
            try {
                properties.load(inputStream);
                hasReadProps = true;
            } catch (Exception e) {
                exception = e;
                try {
                    Thread.sleep(5);
                } catch (InterruptedException ignore) {

                }
            }
        }
        if (!hasReadProps) {
            throw exception;
        }
        return properties;
    }

    /**
     * 分线程更新Properties文件
     *
     * @param filePath   Properties文件路径
     * @param properties 待写入的配置内容
     */
    public static void setProperties(String filePath, Properties properties) {
        SavePropertyThread thread = new SavePropertyThread(filePath, properties);
        executor.execute(thread);
    }

    /**
     * 保存配置文件线程
     *
     * @author sunhongfei01
     * @since 2022-4-19
     */
    private static class SavePropertyThread implements Runnable {

        // 待保存配置文件路径
        private final String filePath;
        // 待保存配置
        private final Properties properties;

        private SavePropertyThread(String filePath, Properties properties) {
            this.filePath = filePath;
            this.properties = properties;
        }


        @Override
        public void run() {
            // 获取待更新配置文件排他锁后更新配置文件.若获取不到文件锁,则不做更新
            try (
                    FileOutputStream outputStream = new FileOutputStream(this.filePath);
                    FileChannel channel = outputStream.getChannel();
                    FileLock fileLock = channel.tryLock()
            ) {
                if (fileLock != null) {
                    properties.store(Channels.newOutputStream(channel), "Last modified on: " + new Date());
                }

            } catch (OverlappingFileLockException e) {
                log.warn("获取元数据缓存文件[{}]文件锁失败!", this.filePath);
            } catch (Exception e) {
                log.warn("写入业务对象中元数据缓存文件[{}]失败!", this.filePath, e);
            }
        }
    }
}

